﻿namespace Blaze.Security
{
    using System;
    using System.Globalization;
    using System.Runtime.InteropServices;

    /// <summary>
    /// 用于内存加密<see cref="float"/>型的变量。
    /// </summary>
    public struct SecureFloat : IEquatable<SecureFloat>
    {
        /// <summary>
        /// 当检测到内存作弊时调用此方法（只会触发一次，之后会被设为null）
        /// </summary>
        public static Action onCheatingDetected;

        private static int cryptoKey = 241887;
        private int currentCryptoKey;
        private FloatIntBytesUnion hiddenValue;
        private float fakeValue;
        private bool isInited;

        private SecureFloat(FloatIntBytesUnion value)
        {
            currentCryptoKey = cryptoKey;
            hiddenValue = value;
            fakeValue = 0;
            isInited = true;
        }

        /// <summary>
        /// 设置新的加密密钥。
        /// </summary>
        /// <param name="newKey">新的密钥</param>
        public static void SetNewCryptoKey(int newKey)
        {
            cryptoKey = newKey;
        }

        /// <summary>
        /// 获取加密后的值。
        /// </summary>
        public int GetEncrypted()
        {
            if (currentCryptoKey != cryptoKey)
            {
                float temp = InternalDecrypt();
                hiddenValue = InternalEncrypt(temp);
                currentCryptoKey = cryptoKey;
            }

            var union = new FloatIntBytesUnion
            {
                b1 = hiddenValue.b4,
                b2 = hiddenValue.b3,
                b3 = hiddenValue.b2,
                b4 = hiddenValue.b1
            };

            return union.i;
        }

        /// <summary>
        /// 将变量设置为加密后的值。
        /// </summary>
        /// <param name="encrypted">加密后的值</param>
        public void SetEncrypted(int encrypted)
        {
            hiddenValue = new FloatIntBytesUnion {i = encrypted};

            if (onCheatingDetected != null)
            {
                fakeValue = InternalDecrypt();
            }
        }

        /// <summary>
        /// 使用默认的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <returns>密文</returns>
        public static int Encrypt(float value)
        {
            return Encrypt(value, cryptoKey);
        }

        /// <summary>
        /// 使用指定的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <param name="key">密钥</param>
        /// <returns>密文</returns>
        public static int Encrypt(float value, int key)
        {
            var u = new FloatIntBytesUnion {f = value};
            u.i = u.i ^ key;

            return u.i;
        }

        private static FloatIntBytesUnion InternalEncrypt(float value, int key = 0)
        {
            var currKey = key;
            if (currKey == 0)
            {
                currKey = cryptoKey;
            }

            var u = new FloatIntBytesUnion {f = value};
            var ret = new FloatIntBytesUnion
            {
                b1 = u.b4,
                b2 = u.b3,
                b3 = u.b2,
                b4 = u.b1,
            };
            ret.i = ret.i ^ currKey;
            return ret;
        }

        /// <summary>
        /// 使用默认的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <returns>明文</returns>
        public static float Decrypt(int value)
        {
            return Decrypt(value, cryptoKey);
        }

        /// <summary>
        /// 使用指定的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <param name="key">密钥</param>
        /// <returns>明文</returns>
        public static float Decrypt(int value, int key)
        {
            var u = new FloatIntBytesUnion {i = value ^ key};
            return u.f;
        }

        private float InternalDecrypt()
        {
            if (!isInited)
            {
                currentCryptoKey = cryptoKey;
                hiddenValue = InternalEncrypt(0);
                fakeValue = 0;
                isInited = true;
            }

            int key = cryptoKey;

            if (currentCryptoKey != cryptoKey)
            {
                key = currentCryptoKey;
            }

            var union = hiddenValue;
            union.i = union.i ^ key;

            var decrypted = new FloatIntBytesUnion
            {
                b1 = union.b4,
                b2 = union.b3,
                b3 = union.b2,
                b4 = union.b1,
            }.f;

            if (onCheatingDetected != null && fakeValue != 0 && Math.Abs(decrypted - fakeValue) > 0.000001f)
            {
                onCheatingDetected();
                onCheatingDetected = null;
            }

            return decrypted;
        }

        [StructLayout(LayoutKind.Explicit)]
        private struct FloatIntBytesUnion
        {
            [FieldOffset(0)] public float f;

            [FieldOffset(0)] public int i;

            [FieldOffset(0)] public byte b1;

            [FieldOffset(1)] public byte b2;

            [FieldOffset(2)] public byte b3;

            [FieldOffset(3)] public byte b4;
        }

        public static implicit operator SecureFloat(float value)
        {
            var obscured = new SecureFloat(InternalEncrypt(value));
            if (onCheatingDetected != null)
            {
                obscured.fakeValue = value;
            }
            return obscured;
        }

        public static implicit operator float(SecureFloat value)
        {
            return value.InternalDecrypt();
        }

        public static SecureFloat operator ++(SecureFloat input)
        {
            var decrypted = input.InternalDecrypt() + 1f;
            input.hiddenValue = InternalEncrypt(decrypted, input.currentCryptoKey);

            if (onCheatingDetected != null)
            {
                input.fakeValue = decrypted;
            }

            return input;
        }

        public static SecureFloat operator --(SecureFloat input)
        {
            var decrypted = input.InternalDecrypt() - 1f;
            input.hiddenValue = InternalEncrypt(decrypted, input.currentCryptoKey);

            if (onCheatingDetected != null)
            {
                input.fakeValue = decrypted;
            }

            return input;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is SecureFloat))
                return false;
            var d = (SecureFloat) obj;
            var dParam = d.InternalDecrypt();
            var dThis = InternalDecrypt();
            if ((double) dParam == (double) dThis)
                return true;
            return float.IsNaN(dParam) && float.IsNaN(dThis);
        }

        public bool Equals(SecureFloat obj)
        {
            float dParam = obj.InternalDecrypt();
            float dThis = InternalDecrypt();


            if ((double) dParam == (double) dThis)
                return true;
            return float.IsNaN(dParam) && float.IsNaN(dThis);
        }

        public override int GetHashCode()
        {
            return InternalDecrypt().GetHashCode();
        }

        public override string ToString()
        {
            return InternalDecrypt().ToString(CultureInfo.InvariantCulture);
        }

        public string ToString(string format)
        {
            return InternalDecrypt().ToString(format);
        }

        public string ToString(IFormatProvider provider)
        {
            return InternalDecrypt().ToString(provider);
        }

        public string ToString(string format, IFormatProvider provider)
        {
            return InternalDecrypt().ToString(format, provider);
        }
    }
}